/*
 * Copyright (C) 2011 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.math;

import java.math.BigInteger;
import java.util.Random;

/**
 * Utilities for benchmarks.
 *
 * In many cases, we wish to vary the order of magnitude of the input as much as we want to vary the
 * input itself, so most methods which generate values use an exponential distribution varying the
 * order of magnitude of the generated values uniformly at random.
 *
 * @author Louis Wasserman
 */
final class MathBenchmarking {
    static final int ARRAY_SIZE = 0x10000;
    static final int ARRAY_MASK = 0x0ffff;
    static final Random RANDOM_SOURCE = new Random(314159265358979L);
    static final int MAX_EXPONENT = 100;

    /*
     * Duplicated from LongMath. binomial(biggestBinomials[k], k) fits in a long, but not
     * binomial(biggestBinomials[k] + 1, k).
     */
    static final int[] biggestBinomials =
            {Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 3810779, 121977, 16175, 4337, 1733, 887, 534, 361,
                    265, 206, 169, 143, 125, 111, 101, 94, 88, 83, 79, 76, 74, 72, 70, 69, 68, 67, 67, 66, 66, 66, 66};

    /**
     * Generates values in a distribution equivalent to randomNonNegativeBigInteger but omitting
     * zero.
     */
    static BigInteger randomPositiveBigInteger(int numBits) {
        BigInteger result;
        do {
            result = randomNonNegativeBigInteger(numBits);
        } while (result.signum() == 0);
        return result;
    }

    /**
     * Generates a number in [0, 2^numBits) with an exponential distribution. The floor of the log2
     * of the result is chosen uniformly at random in [0, numBits), and then the result is chosen in
     * that range uniformly at random. Zero is treated as having log2 == 0.
     */
    static BigInteger randomNonNegativeBigInteger(int numBits) {
        int digits = RANDOM_SOURCE.nextInt(numBits);
        if (digits == 0) {
            return new BigInteger(1, RANDOM_SOURCE);
        } else {
            return new BigInteger(digits, RANDOM_SOURCE).setBit(digits);
        }
    }

    /**
     * Equivalent to calling randomPositiveBigInteger(numBits) and then flipping the sign with 50%
     * probability.
     */
    static BigInteger randomNonZeroBigInteger(int numBits) {
        BigInteger result = randomPositiveBigInteger(numBits);
        return RANDOM_SOURCE.nextBoolean() ? result : result.negate();
    }

    /**
     * Chooses a number in (-2^numBits, 2^numBits) at random, with density concentrated in numbers
     * of lower magnitude.
     */
    static BigInteger randomBigInteger(int numBits) {
        while (true) {
            if (RANDOM_SOURCE.nextBoolean()) {
                return randomNonNegativeBigInteger(numBits);
            }
            BigInteger neg = randomNonNegativeBigInteger(numBits).negate();
            if (neg.signum() != 0) {
                return neg;
            }
        }
    }

    /**
     * Generates a number in [0, 2^numBits) with an exponential distribution. The floor of the log2
     * of the absolute value of the result is chosen uniformly at random in [0, numBits), and then
     * the result is chosen from those possibilities uniformly at random.
     *
     * Zero is treated as having log2 == 0.
     */
    static double randomDouble(int maxExponent) {
        double result = RANDOM_SOURCE.nextDouble();
        result = Math.scalb(result, RANDOM_SOURCE.nextInt(maxExponent + 1));
        return RANDOM_SOURCE.nextBoolean() ? result : -result;
    }

    /**
     * Returns a random integer between zero and {@code MAX_EXPONENT}.
     */
    static int randomExponent() {
        return RANDOM_SOURCE.nextInt(MAX_EXPONENT + 1);
    }

    static double randomPositiveDouble() {
        return Math.exp(randomDouble(6));
    }
}
